/* eslint-disable react/no-access-state-in-setstate */
/**
 * 一个file
 */

import React from 'react';
import PropTypes from 'prop-types';
import { YunxiaoIcon, ButtonIcon } from '@alife/devops-ui';
import hljs from 'utils/hl.js';
import exceed from 'utils/apimap';
import 'components/diff/atom-one-light.scss';
import ListLoading from 'atComponents/ListLoading';
import Issue from './issue';
import { maxBlobSize } from 'utils/constants.js';
import { Message, Balloon } from '@teamix/ui';
import ListEmpty from 'atComponents/ListEmpty';
import './index.scss';
import './file.scss';

// 合并区间
import intl from '../../locale';

function mergeRanges(ranges) {
  if (!(ranges && ranges.length)) {
    return [];
  }

  // Stack of final ranges
  const stack = [];

  // Sort according to start value
  ranges.sort((a, b) => {
    return a[0] - b[0];
  });

  // Add first range to stack
  stack.push(ranges[0]);

  ranges.slice(1).forEach((range) => {
    const top = stack[stack.length - 1];

    if (top[1] < range[0] - 1) {
      // No overlap, push range onto stack
      stack.push(range);
    } else if (top[1] < range[1]) {
      // Update previous range
      top[1] = range[1];
    }
  });

  return stack;
}

export default class File extends React.Component {
  static propTypes = {
    // 在库安全默认展开
    isInProjectSecurity: PropTypes.bool,
    // 在库安全中需要使用引入问题的commitId
    commitId: PropTypes.string,
    // 没有issue时，不调用issue详情接口
    noIssue: PropTypes.bool,
    // 所有展开的起始位置
    all_begin_lines: PropTypes.array,
    // 是否可以收起
    canToggle: PropTypes.bool,
    // 高亮行
    activeLine: PropTypes.number,
    // 额外的issue
    extraIssueList: PropTypes.array,
  };

  static defaultProps = {
    isInProjectSecurity: false,
    commitId: '',
    noIssue: false,
    all_begin_lines: [],
    canToggle: true,
    activeLine: '',
    extraIssueList: [],
  };

  state = {
    blob: null,
    issue_list: [],
    isIssuesVisible: false,
    shownLineRange: [],
    toManyBlobs: false,
    // 文件过大时
    isPayLoadTooLarge: false,
  };

  UNSAFE_componentWillMount() {
    if (this.props.isInProjectSecurity) {
      // this.handleIssue();
      this.onFileToggle();
    }
  }

  // // 传入issue_list需要转成state
  // handleIssue = () => {
  //   const { issue_list, forceUpdateDialog } = this.props;
  //   const shownLineRange = [];
  //   issue_list.forEach((issue) => {
  //     shownLineRange.push([Math.max(issue.begin_line - 3, 1), issue.begin_line + 3]);
  //   });
  //   this.setState({
  //     issue_list,
  //     shownLineRange: mergeRanges(shownLineRange),
  //   });
  //   forceUpdateDialog && forceUpdateDialog();
  // }

  // issueFilter变化时，重置shownLineRange
  // componentDidUpdate(prevProps) {
  //   const { issueFilter } = this.props;
  //   const { issue_list } = this.state;
  //   if (prevProps.issueFilter !== this.props.issueFilter) {
  //     const filteredIssueList = issue_list.filter((issue) => {
  //       if (!issueFilter || issue.level.toLowerCase() === issueFilter) {
  //         return true;
  //       }
  //       return false;
  //     });

  //     const shownLineRange = [];
  //     filteredIssueList.forEach((issue) => {
  //       shownLineRange.push([Math.max(issue.begin_line - 3, 1), issue.begin_line + 3]);
  //     });
  //     this.setState({ shownLineRange: mergeRanges(shownLineRange) });
  //   }
  // }

  getBlob(cb) {
    const { file, build, projectId, commitId, isInProjectSecurity } = this.props;
    exceed
      .fetch({
        api: 'projects.blobs.v4',
        params: { projectId: projectId || window.FORCE.pathResource.id },
        data: {
          filepath: file.file_name,
          // from,
          // to,
          ref: isInProjectSecurity ? commitId : build.sha,
        },
      })
      .then((result) => {
        if (result.content.length > maxBlobSize) {
          this.setState({
            toManyBlobs: true,
          });

          if (this.state.isIssuesVisible) {
            Message.error({
              title: intl.get({
                id: 'code-assets.components.buildResultIssues.file.TheFileContentIsToo',
                defaultMessage: '文件内容太大，只展示问题列表',
              }),
              duration: 2000,
            });
          }
        } else {
          this.setState({
            blob: result.content,
          });
        }

        cb && cb();
      })
      .catch((err) => {
        // 针对文件过大直接不返回问题 errorCode: PAYLOAD_TOO_LARGE
        try {
          const error = err && JSON.parse(err.responseText);
          if (error.errorCode === 'PAYLOAD_TOO_LARGE') {
            this.setState({
              isPayLoadTooLarge: true,
            });

            return;
          }
        } catch (e) {
          console.error(e);
        }
        console.log(err);
        this.setState({
          toManyBlobs: true,
        });
      });
  }

  getIssuesDetail(cb) {
    const { file, build, projectId, isInProjectSecurity, noIssue, extraIssueList } = this.props;
    const { issue_id_list } = file;
    // 在源码漏洞检测中，只有一个issue，其他的步骤无issue，不需要请求
    if (noIssue) {
      const shownLineRange = [];
      extraIssueList.forEach((issue) => {
        shownLineRange.push([Math.max(issue.begin_line - 3, 1), issue.begin_line + 3]);
      });
      this.setState({
        issue_list: extraIssueList,
        shownLineRange: mergeRanges(shownLineRange),
      });

      return;
    }

    const buildId = isInProjectSecurity ? file.build_result_id : build.id;

    const issue_id_length = issue_id_list.length;

    const list_of_issue_ids = [];
    let batch_size;

    if (issue_id_length > 50) {
      // issue大于50分批查询
      batch_size = Math.ceil(issue_id_length / 50);
      for (let i = 0; i < batch_size; i++) {
        list_of_issue_ids[i] = issue_id_list.slice(50 * i, Math.min(50 * (i + 1), issue_id_length));
      }

      for (let i = 0; i < batch_size; i++) {
        const ids = list_of_issue_ids[i].join(',');
        exceed
          .fetch({
            api: 'projects.builds.issues.batch',
            params: { projectId: projectId || window.FORCE.pathResource.id, buildId },
            data: {
              issue_ids: ids,
            },
          })
          .then((result) => {
            result.shift();
            if (extraIssueList && i === 0) {
              result = [...result, ...extraIssueList];
            }

            const shownLineRange = [];
            result.forEach((issue) => {
              shownLineRange.push([Math.max(issue.begin_line - 3, 1), issue.begin_line + 3]);
            });
            this.setState({
              issue_list: [...this.state.issue_list, ...result],
              shownLineRange: mergeRanges([...this.state.shownLineRange, ...shownLineRange]),
            });

            cb && cb();
          })
          .fail(console.log);
      }
    } else {
      exceed
        .fetch({
          api: 'projects.builds.issues.batch',
          params: { projectId: projectId || window.FORCE.pathResource.id, buildId },
          data: {
            issue_ids: issue_id_list.join(','),
          },
        })
        .then((result) => {
          result.shift();
          if (extraIssueList) {
            result = [...result, ...extraIssueList];
          }

          const shownLineRange = [];
          result.forEach((issue) => {
            shownLineRange.push([Math.max(issue.begin_line - 3, 1), issue.begin_line + 3]);
          });
          this.setState({
            issue_list: result,
            shownLineRange: mergeRanges(shownLineRange),
          });

          cb && cb();
        })
        .fail(console.log);
    }
  }

  onFileToggle = () => {
    const { blob, issue_list, forceUpdateDialog } = this.props;

    if (!this.state.isIssuesVisible) {
      if (!blob) {
        this.getBlob(forceUpdateDialog);
      }
      if (!issue_list) {
        this.getIssuesDetail(forceUpdateDialog);
      }
    }

    this.setState({ isIssuesVisible: !this.state.isIssuesVisible }, () => {
      if (!this.state.isIssuesVisible) {
        forceUpdateDialog();
      }
    });
  };

  // 更新展开的行区间
  updateShownLineRange = (range) => {
    const { forceUpdateDialog } = this.props;
    const shownLineRange = [...this.state.shownLineRange];
    shownLineRange.push(range);
    this.setState({ shownLineRange: mergeRanges(shownLineRange) });
    forceUpdateDialog && forceUpdateDialog();
  };

  renderBlobLine = (item, i, totalLineCnt) => {
    const { file, issueFilter, activeLine } = this.props;
    const { shownLineRange, issue_list } = this.state;
    const thisLineNum = i + 1;

    const filteredIssueList = issue_list.filter((issue) => {
      if (!issueFilter || issue.level.toLowerCase() === issueFilter) {
        return true;
      }
      return false;
    });

    let isShownLine;
    let isExpandLine;
    let expandStart;
    let expandEnd;
    shownLineRange.forEach((range, j) => {
      const [start, end] = range;
      if (thisLineNum >= start && thisLineNum <= end) {
        isShownLine = true;
      }
      if (thisLineNum === start - 1) {
        isExpandLine = true;
        expandStart = j === 0 ? 1 : shownLineRange[j - 1][1];
        expandEnd = thisLineNum;
      }
      // 判断最后一个区间之后还有无代码
      if (j === shownLineRange.length - 1 && end < totalLineCnt && thisLineNum === end + 1) {
        isExpandLine = true;
        expandStart = end + 1;
        expandEnd = totalLineCnt;
      }
    });

    if (isExpandLine) {
      return (
        <tr
          className="blob-line expand-line"
          onClick={() => {
            this.updateShownLineRange([expandStart, expandEnd]);
          }}
        >
          <td className="blob-line-num">...</td>
          <td className="blob-line-content">
            {intl.get({
              id: 'code-assets.components.buildResultIssues.file.ClickToExpandMoreRows',
              defaultMessage: '点击展开更多行',
            })}
          </td>
        </tr>
      );
    }

    if (!isShownLine) {
      return null;
    }

    // 在此行下方展示的issue，只匹配 begin_line
    const thisLineShownIssues = filteredIssueList.filter(
      (issue) => issue.begin_line === thisLineNum,
    );
    // issue命中的行，begin_line~end_line区间中均可
    // const thisLineIssues = filteredIssueList.filter(
    //   issue => issue.begin_line <= thisLineNum && issue.end_line >= thisLineNum
    // );
    const thisLineIssues = thisLineShownIssues;

    const fileExt = file.file_name.indexOf('.')
      ? file.file_name.split('.')[file.file_name.split('.').length - 1]
      : '';

    const codeLine = (
      <tr
        className={`blob-line ${activeLine === thisLineNum ? 'active-line' : ''} ${thisLineIssues
          .map((issue) => `blob-line-issue-${issue.id}`)
          .join(' ')}`}
      >
        <td className="blob-line-num" data-number={`line${thisLineNum}`}>
          {thisLineNum}
        </td>
        {fileExt && hljs.getLanguage(fileExt) ? (
          <td
            className="blob-line-content"
            dangerouslySetInnerHTML={{
              __html: hljs.highlight(fileExt, item, true).value,
            }}
          />
        ) : (
          <td className="blob-line-content">{item}</td>
        )}
      </tr>
    );

    if (thisLineShownIssues.length) {
      const issueLine = (
        <tr>
          <td colSpan="3">
            <div className="blob-inline-container">
              {thisLineShownIssues.map((issue) => (
                <Issue issue={issue} file={file} {...this.props} />
              ))}
            </div>
          </td>
        </tr>
      );

      return [codeLine, issueLine];
    }

    return codeLine;
  };

  render() {
    const { file, showIssuesInFile, issueFilter, isInProjectSecurity, commitId, build, canToggle } =
      this.props;
    const { isIssuesVisible, issue_list, blob, toManyBlobs, isPayLoadTooLarge } = this.state;
    // 原始文件
    const originalFilePath = `/${window.FORCE.pathResource.fullPath}/raw/${
      isInProjectSecurity ? commitId : build.sha
    }/${file.file_name}`;

    const filteredIssueList = issue_list.filter((issue) => {
      if (!issueFilter || issue.level.toLowerCase() === issueFilter) {
        return true;
      }
      return false;
    });

    // 根据issue_count_info算出file的level
    const { issue_count_info } = file;
    if (issue_count_info.blocker) {
      file.level = 'Blocker';
    } else if (issue_count_info.critical) {
      file.level = 'Critical';
    } else {
      file.level = 'Major';
    }

    return (
      <div className="file">
        <div
          className={`file-header file-header-level-${
            issueFilter || file.level.toLocaleLowerCase()
          }`}
          onClick={canToggle ? this.onFileToggle : null}
        >
          <div className="file-header-title one-line-ellipsis" title={file.file_name}>
            {file.file_name}
          </div>
          <div className="file-header-right">
            <div className="file-header-right-item" style={{ fontWeight: 'bold' }}>
              {isInProjectSecurity ? (
                <Balloon.Tooltip
                  align="t"
                  trigger={
                    <ButtonIcon
                      type="secondary"
                      component="a"
                      name="folder-line"
                      color="gray"
                      href={`/${window.FORCE.pathResource.fullPath}/blob/${commitId || 'master'}/${
                        file.file_name
                      }`}
                      target="_blank"
                      onClick={(e) => e.stopPropagation()}
                      style={{ position: 'relative', top: 7 }}
                    />
                  }
                >
                  {intl.get({
                    id: 'code-assets.components.buildResultIssues.file.ViewSourceFiles',
                    defaultMessage: '查看源文件',
                  })}
                </Balloon.Tooltip>
              ) : (
                <span>
                  {issueFilter ? file.issue_count_info[issueFilter] : file.issue_id_list.length}
                  {intl.get({
                    id: 'code-assets.components.buildResultIssues.file.Questions',
                    defaultMessage: '个问题',
                  })}
                </span>
              )}
            </div>
            {isInProjectSecurity ? null : (
              <div className="file-header-right-item">
                <YunxiaoIcon size="xs" type={`arrow-${isIssuesVisible ? 'up' : 'down'}-2-line`} />
              </div>
            )}
          </div>
        </div>
        {isPayLoadTooLarge && isIssuesVisible && (
          <div className="blob-container">
            <ListEmpty
              className="build-result-blob-empty"
              image="none"
              content={intl.get({
                id: 'code-assets.components.buildResultIssues.file.SorryTheCurrentFileIs',
                defaultMessage: '抱歉，当前文件过大无法在线浏览',
              })}
              description={
                <a href={originalFilePath} target="_blank" rel="noreferrer">
                  {intl.get({
                    id: 'code-assets.components.buildResultIssues.file.ViewRawFiles',
                    defaultMessage: '查看原始文件',
                  })}
                </a>
              }
            />
          </div>
        )}

        {!isPayLoadTooLarge && isIssuesVisible ? (
          <div>
            {filteredIssueList ? (
              <div>
                {showIssuesInFile && !toManyBlobs ? (
                  <div className="blob-container">
                    {filteredIssueList.filter((item) => !item.begin_line) ? (
                      <div className="file-issues">
                        {filteredIssueList
                          .filter((item) => !item.begin_line)
                          .map((issue) => (
                            <Issue issue={issue} file={file} {...this.props} />
                          ))}
                      </div>
                    ) : null}
                    <table className="blob">
                      <colgroup>
                        <col className="blob-line-num-col" />
                        <col />
                      </colgroup>
                      {blob || blob === '' ? (
                        blob
                          .split('\n')
                          .map((item, i) => this.renderBlobLine(item, i, blob.split('\n').length))
                      ) : (
                        <ListLoading />
                      )}
                    </table>
                  </div>
                ) : (
                  <div className="file-issues">
                    {filteredIssueList.map((issue) => (
                      <Issue
                        issue={issue}
                        file={file}
                        isInProjectSecurity={isInProjectSecurity}
                        {...this.props}
                      />
                    ))}
                  </div>
                )}
              </div>
            ) : (
              <ListLoading />
            )}
          </div>
        ) : null}
      </div>
    );
  }
}
